package qc.module.iot.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import qc.common.core.enums.CommunicationChannelTypeEnum;
import qc.common.core.enums.iot.IotPacketsDataFrameTypeEnum;
import qc.common.core.enums.iot.IotPacketsDataProtocolEnum;
import qc.common.core.enums.iot.IotPacketsFrameFlagEnum;
import qc.common.core.unify.QCUnifyReturnValue;
import qc.common.core.utils.IdentifierUtil;
import qc.common.core.utils.LocalDateTimeUtil;
import qc.common.core.utils.RadixConvertUtil;
import qc.module.iot.dto.MqIotPacketsMessageDto;
import qc.module.iot.entity.IotFramePackets;
import qc.module.iot.repository.IotFramePacketsRepository;
import qc.module.iot.service.producer.IotFramePacketsProducer;

import java.time.LocalDateTime;
import java.util.List;

/**
 * Iot数据帧报文记录Service，提供数据帧报文相关方法
 *
 * @author QuCheng Tech
 * @since 2025/10/28
 */
@Service
public class IotFramePacketsService {
    private static final Logger logger = LoggerFactory.getLogger(IotFramePacketsService.class);

    private static final int Get_Need_Send_Count = 100;//一次获取需要发送至MQ的数据帧最大条数

    @Autowired
    private IotFramePacketsProducer iotPacketsProducer;

    @Autowired
    private IotFramePacketsRepository repository;

    /**
     * 接收到数据帧报文，先发送至MQ如果发送成功由对应的消费者进行保存和解析；如果发送至MQ失败直接保存至数据库中
     * 前端采集设备（如RTU等）发送的数据帧报文（包含心跳包、回复/应答包、数据包），不包含通信和传输协议报文
     *
     * @param channelId       通道ID
     * @param channelName     通道名称
     * @param channelType     通道类型
     * @param packetsSendToMQ 数据帧报文是否先发送至MQ
     * @param bytes           数据帧字节数组
     * @param dataProtocol    数据协议
     * @param frameType       数据帧类型
     * @param frameName       数据帧名称
     * @param stcd            报文中的站点编码/遥测站地址，可以为空
     * @param tm              时标，报文中的数据时标或携带的时标，可以为空
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2025/11/10
     */
    public String receive(String channelId, String channelName, CommunicationChannelTypeEnum channelType, boolean packetsSendToMQ,
                          byte[] bytes, IotPacketsDataProtocolEnum dataProtocol,
                          IotPacketsDataFrameTypeEnum frameType, String frameName,
                          String stcd, LocalDateTime tm) {
        if (bytes != null && bytes.length > 0x0) {
            //ID自动生成
            String id = IdentifierUtil.simpleUUID();
            LocalDateTime now = LocalDateTimeUtil.getNow();

            MqIotPacketsMessageDto message = new MqIotPacketsMessageDto();
            message.setId(id);
            message.setIntm(now);
            message.setBytes(bytes);
            //十六进制字符串自行进行转换，只能使用hex进行存储以保证再次解析为byte数组能保持一致
            String hexString = RadixConvertUtil.toHexString(bytes);
            message.setHex(hexString);
            message.setChannelId(channelId);
            message.setChannelName(channelName);
            message.setChannelType(channelType);
            message.setDataProtocol(dataProtocol);
            message.setFrameType(frameType);
            message.setFrameName(frameName);
            message.setStcd(stcd);
            //设置标记为已发送至MQ
            message.setFlag(IotPacketsFrameFlagEnum.SENDED.getIndex());
            message.setFromDb(false);

            boolean sendToMqSuccess = false;
            if (packetsSendToMQ == true) {
                //使用同步方法发送消息，如果发送消息失败直接保存至数据库
                // 同步发送，等待Broker响应
                String sendResult = iotPacketsProducer.syncSend(message);
                if (StringUtils.isBlank(sendResult))
                    sendToMqSuccess = true;
            }

            // 如果发送到MQ失败，则直接保存到数据库
            if (sendToMqSuccess == false) {
                String saveToDbResult = this.save(message, false);

                if (StringUtils.isBlank(saveToDbResult))
                    return saveToDbResult;
            }
        }

        return QCUnifyReturnValue.Success();
    }

    /**
     * 保存数据帧报文至数据库
     *
     * @param packetsMessage 数据帧报文对象
     * @param hasSendToMQ    是否已发送至MQ
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2025/11/10
     */
    public String save(MqIotPacketsMessageDto packetsMessage, boolean hasSendToMQ) {
        //保存到数据库
        IotFramePackets packets = new IotFramePackets();
        packets.setId(packetsMessage.getId());
        //写入时间使用对象中的时间
        packets.setIntm(packetsMessage.getIntm());
        packets.setChanneltype(packetsMessage.getChannelType());
        packets.setChannelname(packetsMessage.getChannelName());
        packets.setHex(packetsMessage.getHex());
        packets.setDataprotocol(packetsMessage.getDataProtocol());
        packets.setProtocolname(packetsMessage.getDataProtocol().getName());
        packets.setFrametype(packetsMessage.getFrameType());
        packets.setFramename(packetsMessage.getFrameName());
        //数据帧中的时标和站点编码可以为空，不为空时进行赋值
        if (StringUtils.isNotBlank(packetsMessage.getStcd()))
            packets.setStcd(packetsMessage.getStcd());
        //根据传入的是否发送至MQ设置标记
        if (hasSendToMQ == true)
            packets.setFlag(IotPacketsFrameFlagEnum.SENDED.getIndex());
        else
            packets.setFlag(IotPacketsFrameFlagEnum.UN_DEFINE.getIndex());
        //更新时间使用当前时间
        packets.setUptm(LocalDateTimeUtil.getNow());

        repository.insert(packets);

        return QCUnifyReturnValue.Success();
    }

    /**
     * 设置指定记录的解析结果
     *
     * @param id       记录ID
     * @param success  是否解析成功
     * @param info     解析结果信息，不管解析是否成功均可以传入
     * @param stcd     站点编码，可以为空
     * @param datatime 时标，可以为空
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2025/11/18
     */
    public String setParseResult(String id, boolean success, String info, String stcd, LocalDateTime datatime) {
        if (StringUtils.isBlank(id))
            return "记录ID不能为空";

        LocalDateTime now = LocalDateTimeUtil.getNow();
        LambdaUpdateWrapper<IotFramePackets> updateWrapper = new LambdaUpdateWrapper<>();
        //修改标记、解析出的内容、更新时间
        int flag = 0x0;
        //已经完成解析均设置为已发送至MQ
        flag += IotPacketsFrameFlagEnum.SENDED.getIndex();
        if (success == true)
            flag += IotPacketsFrameFlagEnum.PARSE_SUCCESS.getIndex();
        else
            flag += IotPacketsFrameFlagEnum.PARSE_FAIL.getIndex();
        updateWrapper.set(IotFramePackets::getFlag, flag);
        updateWrapper.set(IotFramePackets::getContent, info);
        updateWrapper.set(IotFramePackets::getUptm, now);
        //如果有数据时标或站号信息时进行更新
        if (StringUtils.isNotBlank(stcd))
            updateWrapper.set(IotFramePackets::getStcd, stcd);
        if (datatime != null)
            updateWrapper.set(IotFramePackets::getTm, datatime);

        //条件：id相等
        updateWrapper.eq(IotFramePackets::getId, id);

        int count = repository.update(null, updateWrapper);
        if (count < 0x1)
            return "没有修改任何记录";
        else if (count > 0x1)
            return "修改了" + count + "条记录";

        return QCUnifyReturnValue.Success();
    }

    /**
     * 获取需要发送至MQ的报文数据帧
     *
     * @param maxCount 获取条数，设置为0自动使用默认值
     * @return java.util.List<qc.module.iot.entity.IotFramePackets>
     * @author QuCheng Tech
     * @since 2025/11/19
     */
    public List<IotFramePackets> getNeedSendToMQPackets(int maxCount) {
        if (maxCount <= 0x0)
            maxCount = Get_Need_Send_Count;

        LambdaQueryWrapper<IotFramePackets> wrapper = new LambdaQueryWrapper<>();
        //查询条件:未发送至MQ
        //已发送至MQ很长时间均未处理的不重复查询，可能查询出来发送至MQ处理仍为失败
        IotPacketsFrameFlagEnum notSend = IotPacketsFrameFlagEnum.UN_DEFINE;
        wrapper.eq(IotFramePackets::getFlag, notSend.getIndex());

        //按写入时间先后顺序，先写入的数据先进行解析处理，Asc排序
        wrapper.orderByAsc(IotFramePackets::getIntm);
        //限制最大返回数据条数
        wrapper.last("limit " + maxCount);

        List<IotFramePackets> ens = repository.selectList(wrapper);
        if (ens != null && ens.size() > 0x0)
            return ens;

        return null;
    }
}
